Skip to content

Subsystem 2a: live events poller + #events alerts + event !commands - #13

Merged
HandyS11 merged 14 commits into
developfrom
feat/live-events
Jun 17, 2026
Merged

Subsystem 2a: live events poller + #events alerts + event !commands#13
HandyS11 merged 14 commits into
developfrom
feat/live-events

Conversation

@HandyS11

Copy link
Copy Markdown
Owner

Subsystem 2a — Live Events

First slice of subsystem 2 (Map + live events). Ships the live event layer: a per-connected-server map-marker poller that detects when a cargo ship, patrol helicopter, or chinook enters/leaves the map, alerts each to a per-server #events Discord channel, and answers four in-game !commands. No image rendering — that (plus /map, the #info map field) is deferred to 2b.

What's in

  • Marker poll loop inside ConnectionSupervisor's connected window: fetches map dimensions once on connect, polls GetMapMarkers every ConnectionOptions.MarkerPollInterval (default 10s), diffs snapshots by marker id, and publishes MapMarkersChangedEvent on the bus. First poll after connect is a silent baseline; a failed poll retains the previous snapshot (no spurious despawn/respawn).
  • New RustPlusBot.Features.Events project: a pure MarkerEventClassifier (delta → domain events), a pure GridReference (world coords → "D7"), an in-memory EventStateStore (active markers + a bounded recent-event ring, cleared on disconnect) behind a public IEventState seam, an EventRelay that posts one EN/FR embed per event, and a thin EventsHostedService.
  • Per-server #events channel provisioned by Workspace (read-only) + a public IEventChannelLocator.
  • Four in-game commands: !cargo, !heli, !chinook, !events (read IEventState).

Scope note — CORE-3, not core-4

The design originally included Locked Crate, but the current Rust game no longer sends crate map markers, and the mapped RustPlusApi.Data.MapMarkers facade has no crate bucket. So the shipped set is cargo / patrol helicopter / chinook. MarkerKind.Crate is retained as an unused enum member for forward-compat. Crate/oil-rig detection (which would need an unverified CH47-based workaround) and Explosion events are deferred.

Deferred to 2b

Base-map image rendering (ImageSharp), /map slash variants, #info map image, Explosion events, crate/oil-rig (!small/!large), event-history persistence.

Verification

  • Build 0/0 under strict analyzers (-warnaserror); dotnet jb cleanupcode clean.
  • Full suite 296 green across 8 assemblies (new Features.Events.Tests; Features.Commands +4; Workspace +3 locator).
  • No EF model change — in-memory state only; #events reuses the existing ProvisionedChannel reconciliation. No new gateway intent (markers arrive over the Rust+ socket).
  • The RustPlusSocketSource marker/dimension mapping is the single untested integration shim (consistent with prior slices).

Built subagent-driven (per-task TDD + spec/quality reviews) with a final whole-branch review = READY TO MERGE.

🤖 Generated with Claude Code

HandyS11 and others added 13 commits June 17, 2026 16:15
…vent

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds MarkerPollInterval to ConnectionOptions (default 10s) and a
PollMarkersAsync loop to ConnectionSupervisor: fetches map dimensions
once on connect, polls markers on the configured interval, diffs each
result against the previous snapshot, and publishes MapMarkersChangedEvent
on the event bus. The first poll is a silent baseline; failed polls retain
the previous snapshot without producing a spurious diff. Three integration
tests verify the silent baseline, the added-marker publish, and the
failed-poll snapshot-retention contract.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ript

- FakeRustSocketSource: add source-level EnqueueMarkers that pre-stages
  marker lists and transfers them to FakeConnection at Create time,
  eliminating the setup race between test code and the poll loop.
- FakeConnection: add per-connection ConcurrentQueue<IReadOnlyList<
  MapMarkerSnapshot>> with "hold last" behaviour mirroring NextHeartbeat;
  EnqueueMarkers on the connection available for post-connect injection.
  Existing MarkersResult fallback preserved for Task-2 / no-script callers.
- First_marker_poll_is_a_silent_baseline: rewritten to script poll 1+2 with
  a CargoShip (baseline + no-change) and poll 3 with CargoShip+Heli; waits
  for the definite heli-added event as the race-free signal proving both
  baseline suppression and diff correctness.
- Marker_added_on_a_later_poll_publishes_changed_event: scripted via source
  queue before EnsureConnectionAsync; asserts exact MapDimensions(4000,4000,
  500) from the FakeConnection default.
- Failed_marker_poll_retains_previous_snapshot: polls 1+2 scripted via
  source queue; MarkersThrow used post-event for the throw/recover cycle;
  hold-last means recovery sees the same CargoShip → no spurious event.
- ConnectionOptions.MarkerPollInterval: init → set for IOptions binding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implements the pure event rendering layer: EventLocalizationCatalog with EN/FR
strings for cargo, helicopter, and Chinook events; IEventLocalizer/EventLocalizer
for localization; EventEmbedRenderer to build Discord embeds with grid references.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rkspace spec

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GridReference computed the row count from Width and never read Height/
OceanMargin — correct only because Rust maps are square. Derive each axis
from its own dimension so the grid math is correct by construction.
Behavior-preserving for square maps; GridReference tests unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 17, 2026 16:08

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements “Subsystem 2a” live events by polling Rust+ map markers per connected server, diffing marker snapshots, publishing marker-delta events, relaying classified events to a per-server #events Discord channel, and exposing in-game !cargo/!heli/!chinook/!events commands backed by an in-memory event state store.

Changes:

  • Add marker polling + snapshot diff in ConnectionSupervisor, publishing MapMarkersChangedEvent on the event bus.
  • Introduce new RustPlusBot.Features.Events project (classifier, state store, localization/rendering, relay, hosted service, Discord poster, DI registration).
  • Provision and locate per-server #events channels via Workspace, plus new command handlers and localization strings.

Reviewed changes

Copilot reviewed 56 out of 56 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/RustPlusBot.Features.Workspace.Tests/Locating/EventChannelLocatorTests.cs Verifies #events locator behavior + TTL cache refresh.
tests/RustPlusBot.Features.Events.Tests/State/EventStateStoreTests.cs Tests in-memory active-marker + recent-event tracking.
tests/RustPlusBot.Features.Events.Tests/RustPlusBot.Features.Events.Tests.csproj Adds new Events test project.
tests/RustPlusBot.Features.Events.Tests/Rendering/EventLocalizerTests.cs Tests event string localization (EN/FR, fallback, region normalization).
tests/RustPlusBot.Features.Events.Tests/Rendering/EventEmbedRendererTests.cs Tests embed rendering output for key event kinds.
tests/RustPlusBot.Features.Events.Tests/Relaying/EventRelayTests.cs Tests relay posting behavior + state updates.
tests/RustPlusBot.Features.Events.Tests/Formatting/GridReferenceTests.cs Tests grid formatting (dims + raw coord fallback).
tests/RustPlusBot.Features.Events.Tests/EventRegistrationTests.cs Validates DI registrations resolve for Events feature.
tests/RustPlusBot.Features.Events.Tests/Classifying/MarkerEventClassifierTests.cs Tests delta→domain-event classification rules.
tests/RustPlusBot.Features.Connections.Tests/Fakes/FakeRustSocketSource.cs Extends fake socket with scripted marker polling + dims.
tests/RustPlusBot.Features.Connections.Tests/ConnectionSupervisorTests.cs Adds supervisor tests for baseline suppression, marker diffs, failure retention.
tests/RustPlusBot.Features.Commands.Tests/Handlers/EventHandlersTests.cs Tests new !cargo/!heli/!chinook/!events handlers.
tests/RustPlusBot.Features.Commands.Tests/CommandRegistrationTests.cs Updates command DI registration expectations (+4 handlers).
tests/RustPlusBot.Abstractions.Tests/Connections/MapMarkerSnapshotTests.cs Adds tests for new marker/dimension abstractions.
src/RustPlusBot.Host/RustPlusBot.Host.csproj References new Events feature project from host.
src/RustPlusBot.Host/Program.cs Wires Events into host + validates marker poll interval option.
src/RustPlusBot.Features.Workspace/WorkspaceServiceCollectionExtensions.cs Registers IEventChannelLocator in Workspace DI.
src/RustPlusBot.Features.Workspace/WorkspaceKeys.cs Adds WorkspaceChannelKeys.ServerEvents.
src/RustPlusBot.Features.Workspace/Specs/ServerWorkspaceSpecProvider.cs Provisions per-server #events channel spec.
src/RustPlusBot.Features.Workspace/Locating/IEventChannelLocator.cs Defines #events channel locator interface.
src/RustPlusBot.Features.Workspace/Locating/EventChannelLocator.cs Implements TTL-cached lookup of provisioned #events channels.
src/RustPlusBot.Features.Workspace/Localization/LocalizationCatalog.cs Adds localized channel name for #events.
src/RustPlusBot.Features.Events/State/IEventState.cs Public read seam for active markers + recent events.
src/RustPlusBot.Features.Events/State/EventStateStore.cs In-memory per-(guild,server) active set + bounded recent ring.
src/RustPlusBot.Features.Events/State/ActiveMarker.cs Record for active marker state (coords, dims, seen time).
src/RustPlusBot.Features.Events/RustPlusBot.Features.Events.csproj Adds new Events feature project + references.
src/RustPlusBot.Features.Events/Rendering/IEventLocalizer.cs Localizer interface for event strings.
src/RustPlusBot.Features.Events/Rendering/EventLocalizer.cs Implements culture normalization + fallback localization.
src/RustPlusBot.Features.Events/Rendering/EventLocalizationCatalog.cs Adds EN/FR event string catalog.
src/RustPlusBot.Features.Events/Rendering/EventEmbedRenderer.cs Renders RustMapEvent as a Discord embed.
src/RustPlusBot.Features.Events/Relaying/EventRelay.cs Classifies deltas, updates state, posts embeds to #events.
src/RustPlusBot.Features.Events/Posting/IEventChannelPoster.cs Poster abstraction for Discord channel posting.
src/RustPlusBot.Features.Events/Posting/DiscordEventChannelPoster.cs Discord.Net-backed embed posting implementation.
src/RustPlusBot.Features.Events/Hosting/EventsHostedService.cs Hosted service consuming marker + connection status events.
src/RustPlusBot.Features.Events/Formatting/GridReference.cs World-coords → grid reference (or raw coords).
src/RustPlusBot.Features.Events/EventServiceCollectionExtensions.cs Registers Events services + hosted service in DI.
src/RustPlusBot.Features.Events/Classifying/RustMapEvent.cs Domain event record for classified map events.
src/RustPlusBot.Features.Events/Classifying/MarkerEventClassifier.cs Delta classifier for cargo/heli/chinook events.
src/RustPlusBot.Features.Events/Classifying/MapEventKind.cs Enum for supported live map event types.
src/RustPlusBot.Features.Connections/Supervisor/ConnectionSupervisor.cs Adds marker poll loop + publishes MapMarkersChangedEvent.
src/RustPlusBot.Features.Connections/Listening/RustPlusSocketSource.cs Implements marker polling + map dimension fetch from RustPlusApi.
src/RustPlusBot.Features.Connections/Listening/IRustServerConnection.cs Extends socket contract with marker/dimension APIs.
src/RustPlusBot.Features.Connections/ConnectionOptions.cs Adds MarkerPollInterval option.
src/RustPlusBot.Features.Commands/RustPlusBot.Features.Commands.csproj References Events project for new command handlers.
src/RustPlusBot.Features.Commands/Localization/CommandLocalizationCatalog.cs Adds EN/FR strings for new event commands.
src/RustPlusBot.Features.Commands/Handlers/HeliCommandHandler.cs Adds !heli handler.
src/RustPlusBot.Features.Commands/Handlers/EventsCommandHandler.cs Adds !events handler.
src/RustPlusBot.Features.Commands/Handlers/ChinookCommandHandler.cs Adds !chinook handler.
src/RustPlusBot.Features.Commands/Handlers/CargoCommandHandler.cs Adds !cargo handler.
src/RustPlusBot.Features.Commands/CommandServiceCollectionExtensions.cs Registers new command handlers in DI.
src/RustPlusBot.Abstractions/Events/MapMarkersChangedEvent.cs Adds event-bus payload for marker delta publication.
src/RustPlusBot.Abstractions/Connections/MarkerKind.cs Adds marker-kind enum used by polling/classification.
src/RustPlusBot.Abstractions/Connections/MapMarkerSnapshot.cs Adds marker snapshot abstraction.
src/RustPlusBot.Abstractions/Connections/MapDimensions.cs Adds map dimensions abstraction for grid rendering.
RustPlusBot.slnx Includes new Events projects in solution.
docs/development/running-locally.md Documents #events channel behavior for local runs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/RustPlusBot.Features.Events/Rendering/EventEmbedRenderer.cs
Comment thread src/RustPlusBot.Features.Commands/Handlers/EventsCommandHandler.cs
Comment thread src/RustPlusBot.Features.Connections/Listening/IRustServerConnection.cs Outdated
Comment thread src/RustPlusBot.Features.Events/Posting/DiscordEventChannelPoster.cs Outdated
Comment thread docs/development/running-locally.md Outdated
- EventEmbedRenderer + EventsCommandHandler: throw ArgumentOutOfRangeException
  on an unknown MapEventKind instead of silently falling back to the chinook
  key (fail-fast if a future kind is added).
- IRustServerConnection.GetMapMarkersAsync: correct the stale "all kinds /
  MarkerKind.Other" XML doc — the mapped facade only surfaces cargo/heli/chinook.
- RustPlusSocketSource.AddMarkers: skip markers with null X/Y instead of
  substituting (0,0), which would diff as a phantom spawn/despawn at the origin.
- DiscordEventChannelPoster: forward the CancellationToken via
  RequestOptions.CancelToken (and rethrow OperationCanceledException so a
  shutdown cancel isn't logged as a post failure).
- running-locally.md: chinook only alerts on spawn (cargo/heli on enter+leave) —
  reword the #events description to match actual behavior.
- EventStateStoreTests: rename the un-alerted-removal test for clarity
  (behavior was already correct).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HandyS11
HandyS11 merged commit d4712b8 into develop Jun 17, 2026
3 checks passed
@HandyS11
HandyS11 deleted the feat/live-events branch June 17, 2026 16:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants